package com.code2roc.xhood.asm;

import com.code2roc.xhood.model.ObscureConfig;
import com.code2roc.xhood.model.ObsureEnum;
import com.code2roc.xhood.util.CommonUtil;
import com.code2roc.xhood.util.ObscureUtil;
import org.apache.commons.lang3.StringUtils;
import org.objectweb.asm.*;

import java.io.File;
import java.io.FileInputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;

public class MethodDefineVisitor extends ClassVisitor {
    private String packageName;
    private String className;
    private String classPackageName;
    private String superName;
    private List<String> interfaceList;
    private List<String> refreshList;
    private List<String> obscurePackages;
    private List<String> obscureIgnorePackages;
    private List<String> obscureIgnoreClasss;
    private String outPutPath;
    private HashMap<String, List<String>> methodAnnotationMap;

    public MethodDefineVisitor(ClassVisitor cv, ObscureConfig obscureConfig, HashMap<String, List<String>> methodAnnotationMap) {
        super(Opcodes.ASM9, cv);
        interfaceList = new ArrayList<>();
        refreshList = new ArrayList<>();
        this.packageName = obscureConfig.getPackageName();
        this.obscurePackages = obscureConfig.getPackages();
        this.obscureIgnorePackages = obscureConfig.getIgnorePackages();
        this.obscureIgnoreClasss = obscureConfig.getIgnoreClasss();
        this.outPutPath = obscureConfig.getOutPutPath();
        this.methodAnnotationMap = methodAnnotationMap;
    }

    @Override
    public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
        super.visit(version, access, name, signature, superName, interfaces);
        this.className = name.replace('/', '.');
        List<String> classNameList = new ArrayList<>(Arrays.asList(className.split("\\.")));
        classNameList.remove(classNameList.size() - 1);
        this.classPackageName = StringUtils.join(classNameList, ".");
        this.superName = superName.replace('/', '.');
        for (String item : interfaces) {
            interfaceList.add(item.replace('/', '.'));
        }
    }

    @Override
    public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
        boolean modifyInvoke = true;
        MethodVisitor mv;
        if (superName.equals("java.lang.Enum")) {
            modifyInvoke = false;
        }
        if (modifyInvoke) {
            modifyInvoke = checkPackage(classPackageName);
        }
        if (modifyInvoke) {
            modifyInvoke = checkClass(className);
        }
        if (modifyInvoke) {
            modifyInvoke = checkName(name);
        }
        if (modifyInvoke) {
            modifyInvoke = checkMethodType(name, desc);
        }
        if (modifyInvoke) {
            modifyInvoke = checkAnnotation(name, desc);
        }
        if (modifyInvoke) {
            String obfuscatedName = ObscureUtil.getObscureNameV3(className,name, ObsureEnum.Method);
            mv = super.visitMethod(access, obfuscatedName, desc, signature, exceptions);
            refreshList.add(className + "." + name + "|" + desc);
        } else {
            mv = super.visitMethod(access, name, desc, signature, exceptions);
        }
        return new EmptyAdapter(mv);
    }

    private boolean checkPackage(String packagePathName) {
        boolean modifyInvoke = true;
        if (!packagePathName.startsWith(packageName)) {
            modifyInvoke = false;
        }
        boolean exist = false;
        for (String p : obscurePackages) {
            if (packagePathName.startsWith(p)) {
                exist = true;
                break;
            }
        }
        if (!exist) {
            modifyInvoke = false;
        }
        for (String p : obscureIgnorePackages) {
            if (p.startsWith("*")) {
                if (packagePathName.endsWith(p.replace("*/", ""))) {
                    modifyInvoke = false;
                    break;
                }
            } else {
                if (p.equals(packagePathName)) {
                    modifyInvoke = false;
                    break;
                }
            }
        }
        return modifyInvoke;
    }

    private boolean checkClass(String classPathName) {
        boolean modifyInvoke = true;
        for (String p : obscureIgnoreClasss) {
            if (p.startsWith("*")) {
                if (classPathName.endsWith(p.replace("*", ""))) {
                    modifyInvoke = false;
                    break;
                }
            } else {
                if (p.equals(classPathName)) {
                    modifyInvoke = false;
                    break;
                }
            }
        }
        return modifyInvoke;
    }

    public boolean checkMethodType(String methodName, String methodDes) {
        boolean modifyInvoke = true;
        HashMap<String, String> paramMap = new HashMap();
        ObscureUtil.findAllParentMethod(outPutPath,methodName, methodDes, superName, paramMap);
        for (String interfaceName : interfaceList) {
            ObscureUtil.findAllParentMethod(outPutPath,methodName, methodDes, interfaceName, paramMap);
        }
        for (String key : paramMap.keySet()) {
            String itemClassName = paramMap.get(key);
            List<String> itemClassNameList = new ArrayList<>(Arrays.asList(itemClassName.split("\\.")));
            itemClassNameList.remove(itemClassNameList.size() - 1);
            String itemPackageName = StringUtils.join(itemClassNameList, ".");
            if (!checkPackage(itemPackageName)) {
                modifyInvoke = false;
            }
            if (!checkClass(itemClassName)) {
                modifyInvoke = false;
            }

        }
        return modifyInvoke;
    }

    private boolean checkName(String methodName) {
        boolean modifyInvoke = true;
        if (methodName.equals("main") || methodName.startsWith("lambda") || ObscureUtil.isConstructor(methodName)) {
            modifyInvoke = false;
        }
        return modifyInvoke;
    }

    private boolean checkAnnotation(String methodName, String methodDes) {
        boolean modifyInvoke = true;
        String key = className + "." + methodName + "|" + methodDes;
        if (methodAnnotationMap.containsKey(key)) {
            List<String> anntationList = methodAnnotationMap.get(key);
            if (anntationList.contains("Lorg/aspectj/lang/annotation/Pointcut;")) {
                modifyInvoke = false;
            }
        }
        return modifyInvoke;
    }

    public List<String> getRefreshList() {
        return refreshList;
    }
}
